#include <sstream>
#include <sys/types.h>      // typy danych do wywolan systemowych
#include <sys/socket.h>     // gniazda BSD
#include <netdb.h>
#include <arpa/inet.h>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>
#include "TCPClient.h"
#include "TCPCommon.h"
#include "../CommError.h"
#include "../../smtp/data/SMTPDefaults.h"
#include "../../system/Observers.h"

#define foreach BOOST_FOREACH

using namespace kc_sf;

// -------------------------------------------------------------- //

TCPClient::TCPClient ()
: CommClient (),
  cl_sock (-1),
  cl_comm( NULL )
{
}

// -------------------------------------------------------------- //

TCPClient::~TCPClient ()
{
	disconnect();
}

// -------------------------------------------------------------- //

void TCPClient::connectToSrv ( const std::string &srv_name, int srv_port )
{
	if (cl_sock != -1)
		return;

	struct addrinfo *hints = new addrinfo();
	struct addrinfo *result, *rp;

	hints->ai_family = AF_UNSPEC;		/* Allow IPv4 or IPv6 */
	hints->ai_socktype = SOCK_STREAM;	/* Stream socket */
	hints->ai_flags = 0;
	hints->ai_protocol = 0;				/* Any protocol */

	const char *srv_port_str = ( boost::lexical_cast <std::string>( srv_port ) ).c_str();
	if( getaddrinfo( srv_name.c_str(), srv_port_str, hints, &result ) != 0 )
		throw CommError( FailedConnection, "Nie udało się otworzyć gniazda" );

	delete hints;

	for( rp = result; rp != NULL; rp = rp->ai_next )
	{
		cl_sock = socket( rp->ai_family, rp->ai_socktype, rp->ai_protocol );
		if( cl_sock == -1 )
			continue;

		if( connect( cl_sock, rp->ai_addr, rp->ai_addrlen ) != -1 )
			break;                  /* Success */

		std::stringstream err_stream;
		err_stream << "Nie można nawiązać połączenia TCP z hostem "
			<< srv_name << ":" << srv_port;

		throw CommError( FailedConnection, err_stream.str() );
	}

	cl_cid = ConnectionsOutObserver.add() - 1;
	AllConnectionsOutObserver.add();
	cl_logger << info << sender << cidOut( cl_cid ) << "Połączono z serwerem SMTP: " << srv_name << ":" << srv_port << flush;

	cl_comm = new TCPCommon( cl_sock, MAX_LINE_LENGTH );
}

// -------------------------------------------------------------- //

bool TCPClient::isConnected () const
{
	return cl_sock != -1;
}

// -------------------------------------------------------------- //

void TCPClient::disconnect()
{
	if (cl_sock != -1)
	{
		close (cl_sock);
		cl_sock = -1;
		cl_logger << info << sender << cidOut( cl_cid ) << "Rozłączono z serwerem SMTP" << flush;
	}
	if( cl_comm )
	{
		delete cl_comm;
		cl_comm = NULL;
	}
}

// -------------------------------------------------------------- //

void TCPClient::recvData( std::string &to, const std::string &until_str, int timeout )
{
	if( cl_comm == NULL )
		throw CommError( FailedConnection, "Nie połączono z serwerem" );

	if( cl_comm->recvData( to, until_str, timeout ) != Success )
	{
		disconnect ();
		throw CommError( FailedRead, "Błąd odczytu TCPClient::recvData()" );
	}
    cl_logger << debug << sender << cidOut( cl_cid ) << " Odebrano: \""<< to  << "\"" << flush;
}

// -------------------------------------------------------------- //

void TCPClient::sendData( const std::string &what, int timeout )
{
	if( cl_comm == NULL )
		throw CommError( FailedConnection, "Nie połączono z serwerem" );

	if( cl_comm->sendData( what, timeout ) != Success )
	{
		disconnect ();
		throw CommError( FailedWrite, "Błąd zapisu TCPClient::sendData()" );
	}
    cl_logger << debug << sender << cidOut( cl_cid ) << " Wysłano: \""<< what  << "\"" << flush;
}
